package input;

import filehandler.FileHandler;
import filehanlderimpl.DiskFileHandler;
import filehanlderimpl.StdInHandler;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;


/**
 * 输入系统
 * Created by weijianyu on 2017/1/16.
 */
public class Input {
    private final int MAXLOOK = 16; // look ahead 最多的字符数
    private final int MAXLEX = 1024;    // 分词后字符串的最大长度
    private final int BUFSIZE = (MAXLEX * 3) + (2 * MAXLOOK);   // 缓冲区大小3个MAXLEX+2个MAXLOOK
    private int end_buf = BUFSIZE;  // 缓冲区逻辑结束地址
    private final int DANGER = (end_buf - MAXLOOK); // danger区域大小
    private final int END = BUFSIZE; // 缓冲区物理结束地址
    private final byte[] start_buf = new byte[BUFSIZE]; // 缓冲区
    private int next = END; // 指向当前要读入的字符位置
    private int pMark = END;    // 上一个被词法分析器分析的字符串的起始位置
    private int sMark = END;    // 当前被词法分析器分析的字符串的起始位置也是上一个字符串开始位置
    private int eMark = END;    // 当前被词法分析器分析的字符串的结束位置
    private int pLineno = 0;    // 上一个被词法分析器分析的字符串所在的行号
    private int pLength = 0;    // 上一个被词法分析器分析的字符串长度
    private FileHandler fileHandler = null;
    private int lineno = 1; // 当前被词法分析器分析字符串的行号
    private int Mline = 1;
    private boolean eof_read = false;   // 输入流中是否还有可读信息

    /**
     * 缓冲区是否还有可读信息
     *
     * @return true/false  是/否
     */
    private boolean noMoreChars() {
        return (eof_read && next >= end_buf);
    }

    /**
     * 获取输入流
     * 如果fileName存在获取一个文件输入流
     * 不存在获取一个客户端输入流
     *
     * @param fileName 文件路径
     * @return 输入流
     */
    private FileHandler getFileHandler(String fileName) {
        if (fileName != null) {
            // 文件读取形式
            return new DiskFileHandler(fileName);
        } else {
            // 控制台输入流
            return new StdInHandler();
        }
    }

    /**
     * 获取输入流并打开，初始化参数
     *
     * @param fileName 文件路径
     */
    public void newFile(String fileName) {
        if (fileHandler != null) {
            fileHandler.close();
        }
        fileHandler = getFileHandler(fileName);
        fileHandler.open();

        eof_read = false;
        next = END;
        sMark = END;
        eMark = END;
        end_buf = END;
        lineno = 1;
        Mline = 1;
    }

    /**
     * 获取当前处理的字符串
     *
     * @return 返回当前处理的字符串
     */
    public String sText() {
        byte[] str = Arrays.copyOfRange(start_buf, sMark, sMark + sLength());
        return new String(str, StandardCharsets.UTF_8);
    }

    /**
     * 读取当前字符串长度
     *
     * @return 当前读取字符串的长度
     */
    public int sLength() {
        return (eMark - sMark);
    }

    /**
     * 获取当前的行号
     *
     * @return 返回当前行号
     */
    public int sLineNo() {
        return lineno;
    }

    /**
     * 获取上一个字符串
     *
     * @return 返回上一个字符串
     */
    public String pText() {
        byte[] str = Arrays.copyOfRange(start_buf, pMark, pMark + preLength());
        return new String(str, StandardCharsets.UTF_8);
    }

    /**
     * 获取上一个字符串长度
     *
     * @return 返回一个字符串长度
     */
    public int preLength() {
        return pLength;
    }

    /**
     * 获取上一个字符串所在行号
     *
     * @return 返回上一个字符串所在行号
     */
    public int pLineNo() {
        return pLineno;
    }

    /**
     * 将eMark、sMark设置成next
     *
     * @return 返回sMark开始
     */
    public int markStart() {
        Mline = lineno;
        eMark = sMark = next;
        return sMark;
    }

    /**
     * 将eMark设置成next
     *
     * @return 返回eMark结束
     */
    public int markEnd() {
        Mline = lineno;
        eMark = next;
        return eMark;
    }

    /**
     * 将是sMark移动一个单位
     *
     * @return 移动后smark
     */
    public int moveStart() {
        if (sMark >= eMark) {
            return -1;
        } else {
            sMark++;
            return sMark;
        }
    }

    /**
     * 将next预读取消
     *
     * @return next位置
     */
    public int toMark() {
        lineno = Mline;
        next = eMark;
        return next;
    }

    /**
     * 将上一个被词法解析器解析的字符串更新当前
     *
     * @return 返回pmark
     */
    public int markPrev() {
        pMark = sMark;
        pLineno = lineno;
        pLength = eMark - sMark;
        return pMark;
    }

    public static final int NO_MORE_CHARS_TO_READ = 0;
    public static final int FLUSH_OK = 1;
    public static final int FLUSH_FAIL = -1;

    /**
     * 刷新缓冲区，预读MaxLex整数倍字符
     *
     * @param force 刷新条件
     * @return 是否成功
     */
    private int flush(boolean force) {
        /*
         * flush 缓冲区，如果Next 没有越过Danger的话，那就什么都不做
		 * 要不然像上一节所说的一样将数据进行平移，并从输入流中读入数据，写入平移后
		 * 所产生的空间
		 *                            pMark                     DANGER
		 *                              |                          |
		 *     Start_buf              sMark         eMark          | Next  End_buf
		 *         |                    | |           |            |  |      |
		 *         V                    V V           V            V  V      V
		 *         +---------------------------------------------------------+---------+
		 *         | 已经读取的区域        |          未读取的区域                 | 浪费的区域|
		 *         +--------------------------------------------------------------------
		 *         |<---shift_amt------>|<-----------copy_amt--------------->|
		 *         |<-------------------------BUFSIZE---------------------------------->|
		 *
		 *  未读取区域的左边界是pMark或sMark(两者较小的那个),把 未读取区域平移到最左边覆盖已经读取区域，返回1
		 *  如果flush操作成功，-1如果操作失败，0 如果输入流中已经没有可以读取的多余字符。如果force 为 true
		 *  那么不管Next有没有越过Danger,都会引发Flush操作
		 */
        int copy_amt, shiflt_amt, left_edge;
        if (noMoreChars()) {
            return NO_MORE_CHARS_TO_READ;
        }
        if (eof_read) {
            // 输入流已经没有多余的信息
            return FLUSH_OK;
        }
        if (next > DANGER || force) {
            left_edge = pMark < sMark ? pMark : sMark;
            shiflt_amt = left_edge;
            if (shiflt_amt < MAXLEX) {
                if (!force) {
                    return FLUSH_FAIL;
                }
                left_edge = markStart();
                markPrev();
                shiflt_amt = left_edge;
            }
            copy_amt = end_buf - left_edge;
            System.arraycopy(start_buf, 0, start_buf, left_edge, copy_amt);
            if (fillbuf(copy_amt) == 0) {
                System.out.println("Internal Error, ii_flush: Buffer full, can't read");
            }
            if (pMark != 0) {
                pMark -= shiflt_amt;
            }
            sMark -= shiflt_amt;
            eMark -= shiflt_amt;
            next -= shiflt_amt;
        }
        return FLUSH_OK;
    }

    /**
     * 读取字符
     *
     * @param starting_at 开始位置
     * @return 读取实际数量
     */
    public int fillbuf(int starting_at) {
        /*
         * 从输入流中读取信息，填充缓冲区平移后的可用空间，可用空间的长度是从starting_at一直到End_buf
		 * 每次从输入流中读取的数据长度是MAXLEX写整数倍
		 *
		 */
        int need; // 需要从输入流中读入的数据长度
        int got = 0; // 实际上从输入流中读到的数据长度
        need = ((END - starting_at) / MAXLEX) * MAXLEX;
        if (need < 0) {
            System.out.println("Internal Error (ii_fillbuf): Bad read-request starting addr.");
        }
        if (need == 0) {
            return 0;
        }
        if ((got = fileHandler.read(start_buf, starting_at, need)) == -1) {
            System.out.println("Can't read input file");
        }
        end_buf = starting_at + got;
        if (got < need) {
            // 输入流已经到末尾
            eof_read = false;
        }
        return got;
    }

    public byte advance() {
        /*
         * ii_advance() 是真正的获取输入函数，他将数据从输入流中读入缓冲区，并从缓冲区中返回要读取的字符
		 * 并将Next加一，从而指向下一个要读取的字符, 如果Next的位置距离缓冲区的逻辑末尾(End_buf)不到
		 * MAXLOOK 时， 将会对缓冲区进行一次flush 操作
		 */
        if (noMoreChars()) {
            return 0;
        }

        if (!eof_read && flush(false) < 0) {
            // 从输入流读入数据到缓冲区时出错
            return -1;
        }
        if (start_buf[next] == '\n') {
            lineno++;
        }
        return start_buf[next++];
    }

}
